🔗 FBro协议处理与外部程序唤醒指南
完全控制网页链接协议处理,实现与外部应用程序的无缝集成
📋 概述
FBro浏览器提供了强大的协议处理功能,允许开发者控制当用户点击特定协议链接时的行为。通过重写OnProtocolExecution方法,可以决定是否允许操作系统执行外部程序,实现与各种应用程序的深度集成。
🎯 主要功能
- 自定义协议处理:控制bdnetdisk://、mailto:、tel:等协议
- 外部程序唤醒:安全地启动系统关联的应用程序
- 协议拦截分析:记录和分析协议请求
- 安全控制:防止恶意协议攻击
📱 常见协议示例
| 协议类型 | 示例 | 关联程序 |
|---|---|---|
bdnetdisk:// | 百度网盘下载链接 | 百度网盘客户端 |
thunder:// | 迅雷下载链接 | 迅雷下载器 |
mailto: | 邮件链接 | 默认邮件客户端 |
tel: | 电话链接 | 电话应用 |
skype: | Skype通话 | Skype客户端 |
steam:// | Steam游戏 | Steam平台 |
🔧 核心实现
OnProtocolExecution方法详解
csharp
/// <summary>
/// 协议执行处理器
/// 当浏览器遇到自定义协议链接时触发
/// </summary>
/// <param name="browser">触发协议的浏览器实例</param>
/// <param name="frame">触发协议的框架</param>
/// <param name="request">协议请求信息</param>
/// <param name="allow_os_execution">是否允许操作系统执行外部程序</param>
public override void OnProtocolExecution(IFBroSharpBrowser browser,
IFBroSharpFrame frame,
IFBroSharpRequest request,
ref bool allow_os_execution)
{
// 获取协议信息
string url = request.Url;
string protocol = GetProtocolFromUrl(url);
Console.WriteLine($"协议执行请求 - URL: {url}");
Console.WriteLine($"协议类型: {protocol}");
Console.WriteLine($"来源框架: {frame?.Url ?? "Unknown"}");
// 根据协议类型和安全策略决定是否允许执行
bool shouldAllow = DetermineProtocolExecution(protocol, url, browser, frame);
// 记录协议处理日志
LogProtocolExecution(protocol, url, shouldAllow);
// 设置是否允许操作系统执行
allow_os_execution = shouldAllow;
if (shouldAllow)
{
Console.WriteLine($"✅ 允许执行协议: {protocol}");
}
else
{
Console.WriteLine($"❌ 阻止执行协议: {protocol}");
}
}📖 参数说明
| 参数名 | 类型 | 说明 |
|---|---|---|
browser | IFBroSharpBrowser | 触发协议的浏览器实例 |
frame | IFBroSharpFrame | 触发协议的框架对象 |
request | IFBroSharpRequest | 包含协议URL和其他请求信息 |
allow_os_execution | ref bool | 控制是否允许操作系统执行外部程序 |
IFBroSharpRequest关键属性
csharp
/// <summary>
/// 获取请求的关键信息
/// </summary>
private void AnalyzeRequest(IFBroSharpRequest request)
{
string url = request.Url; // 完整的协议URL
string method = request.Method; // 请求方法(通常为GET)
string referrer = request.ReferrerUrl; // 引荐来源URL
Console.WriteLine($"请求URL: {url}");
Console.WriteLine($"请求方法: {method}");
Console.WriteLine($"引荐来源: {referrer}");
}🎯 协议处理策略
1. 智能协议识别
csharp
/// <summary>
/// 从URL提取协议类型
/// </summary>
/// <param name="url">完整URL</param>
/// <returns>协议名称</returns>
private string GetProtocolFromUrl(string url)
{
try
{
var uri = new Uri(url);
return uri.Scheme.ToLower();
}
catch
{
// 如果URL格式不正确,尝试手动解析
int colonIndex = url.IndexOf(':');
if (colonIndex > 0)
{
return url.Substring(0, colonIndex).ToLower();
}
return "unknown";
}
}
/// <summary>
/// 协议分类枚举
/// </summary>
public enum ProtocolCategory
{
/// <summary>
/// 下载协议
/// </summary>
Download,
/// <summary>
/// 通信协议
/// </summary>
Communication,
/// <summary>
/// 媒体协议
/// </summary>
Media,
/// <summary>
/// 游戏协议
/// </summary>
Gaming,
/// <summary>
/// 系统协议
/// </summary>
System,
/// <summary>
/// 未知协议
/// </summary>
Unknown
}
/// <summary>
/// 获取协议分类
/// </summary>
/// <param name="protocol">协议名称</param>
/// <returns>协议分类</returns>
private ProtocolCategory GetProtocolCategory(string protocol)
{
switch (protocol.ToLower())
{
// 下载类协议
case "bdnetdisk":
case "thunder":
case "flashget":
case "qqdl":
return ProtocolCategory.Download;
// 通信类协议
case "mailto":
case "tel":
case "sms":
case "skype":
case "zoom":
case "teams":
return ProtocolCategory.Communication;
// 媒体类协议
case "potplayer":
case "vlc":
case "spotify":
return ProtocolCategory.Media;
// 游戏类协议
case "steam":
case "origin":
case "uplay":
return ProtocolCategory.Gaming;
// 系统类协议
case "ms-settings":
case "ms-windows-store":
return ProtocolCategory.System;
default:
return ProtocolCategory.Unknown;
}
}2. 智能决策引擎
csharp
/// <summary>
/// 协议执行决策配置
/// </summary>
public class ProtocolExecutionConfig
{
/// <summary>
/// 是否启用协议处理
/// </summary>
public bool EnableProtocolHandling { get; set; } = true;
/// <summary>
/// 允许的协议白名单
/// </summary>
public List<string> AllowedProtocols { get; set; } = new List<string>
{
"bdnetdisk", "thunder", "mailto", "tel"
};
/// <summary>
/// 禁止的协议黑名单
/// </summary>
public List<string> BlockedProtocols { get; set; } = new List<string>();
/// <summary>
/// 受信任的来源域名
/// </summary>
public List<string> TrustedDomains { get; set; } = new List<string>
{
"pan.baidu.com", "yunpan.360.cn", "cloud.189.cn"
};
/// <summary>
/// 是否需要用户确认
/// </summary>
public bool RequireUserConfirmation { get; set; } = false;
/// <summary>
/// 是否记录协议执行日志
/// </summary>
public bool EnableLogging { get; set; } = true;
}
/// <summary>
/// 配置实例
/// </summary>
private ProtocolExecutionConfig config = new ProtocolExecutionConfig();
/// <summary>
/// 智能决策是否允许协议执行
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">完整URL</param>
/// <param name="browser">浏览器实例</param>
/// <param name="frame">框架实例</param>
/// <returns>是否允许执行</returns>
private bool DetermineProtocolExecution(string protocol, string url,
IFBroSharpBrowser browser, IFBroSharpFrame frame)
{
// 1. 检查是否启用协议处理
if (!config.EnableProtocolHandling)
{
Console.WriteLine("协议处理已禁用");
return false;
}
// 2. 检查黑名单
if (config.BlockedProtocols.Contains(protocol))
{
Console.WriteLine($"协议 {protocol} 在黑名单中");
return false;
}
// 3. 检查白名单
if (config.AllowedProtocols.Any() && !config.AllowedProtocols.Contains(protocol))
{
Console.WriteLine($"协议 {protocol} 不在白名单中");
return false;
}
// 4. 检查来源可信度
if (frame != null && !IsTrustedSource(frame.Url))
{
Console.WriteLine($"来源不可信: {frame.Url}");
return false;
}
// 5. 根据协议类型进行特殊判断
if (!ValidateProtocolSafety(protocol, url))
{
Console.WriteLine($"协议安全验证失败: {protocol}");
return false;
}
// 6. 用户确认(如果需要)
if (config.RequireUserConfirmation)
{
return RequestUserConfirmation(protocol, url);
}
return true;
}
/// <summary>
/// 检查来源是否可信
/// </summary>
/// <param name="sourceUrl">来源URL</param>
/// <returns>是否可信</returns>
private bool IsTrustedSource(string sourceUrl)
{
if (string.IsNullOrEmpty(sourceUrl))
return false;
try
{
var uri = new Uri(sourceUrl);
string domain = uri.Host.ToLower();
return config.TrustedDomains.Any(trustedDomain =>
domain.Equals(trustedDomain, StringComparison.OrdinalIgnoreCase) ||
domain.EndsWith("." + trustedDomain, StringComparison.OrdinalIgnoreCase));
}
catch
{
return false;
}
}
/// <summary>
/// 协议安全验证
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">完整URL</param>
/// <returns>是否安全</returns>
private bool ValidateProtocolSafety(string protocol, string url)
{
switch (protocol.ToLower())
{
case "bdnetdisk":
return ValidateBaiduNetdiskUrl(url);
case "thunder":
return ValidateThunderUrl(url);
case "mailto":
return ValidateMailtoUrl(url);
case "tel":
return ValidateTelUrl(url);
default:
// 对于未知协议,采用保守策略
return config.AllowedProtocols.Contains(protocol);
}
}
/// <summary>
/// 验证百度网盘协议URL
/// </summary>
/// <param name="url">百度网盘URL</param>
/// <returns>是否有效</returns>
private bool ValidateBaiduNetdiskUrl(string url)
{
// 简单验证:确保URL格式正确
return url.StartsWith("bdnetdisk://") && url.Length > 20;
}
/// <summary>
/// 验证迅雷协议URL
/// </summary>
/// <param name="url">迅雷URL</param>
/// <returns>是否有效</returns>
private bool ValidateThunderUrl(string url)
{
return url.StartsWith("thunder://") && url.Length > 15;
}
/// <summary>
/// 验证邮件协议URL
/// </summary>
/// <param name="url">邮件URL</param>
/// <returns>是否有效</returns>
private bool ValidateMailtoUrl(string url)
{
try
{
// 检查是否包含有效的邮件地址
return url.StartsWith("mailto:") && url.Contains("@");
}
catch
{
return false;
}
}
/// <summary>
/// 验证电话协议URL
/// </summary>
/// <param name="url">电话URL</param>
/// <returns>是否有效</returns>
private bool ValidateTelUrl(string url)
{
// 简单验证:确保包含数字
var phoneNumber = url.Substring(4); // 移除 "tel:"
return System.Text.RegularExpressions.Regex.IsMatch(phoneNumber, @"[\d\-\+\(\)\s]+");
}3. 用户交互确认
csharp
/// <summary>
/// 请求用户确认是否执行协议
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">完整URL</param>
/// <returns>用户是否确认</returns>
private bool RequestUserConfirmation(string protocol, string url)
{
try
{
string message = GenerateConfirmationMessage(protocol, url);
var result = System.Windows.Forms.MessageBox.Show(
message,
"协议执行确认",
System.Windows.Forms.MessageBoxButtons.YesNo,
System.Windows.Forms.MessageBoxIcon.Question,
System.Windows.Forms.MessageBoxDefaultButton.Button2 // 默认选择"否"
);
bool confirmed = result == System.Windows.Forms.DialogResult.Yes;
Console.WriteLine($"用户确认结果: {(confirmed ? "允许" : "拒绝")}");
return confirmed;
}
catch (Exception ex)
{
Console.WriteLine($"用户确认失败: {ex.Message}");
return false; // 出错时默认拒绝
}
}
/// <summary>
/// 生成确认消息
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">完整URL</param>
/// <returns>确认消息</returns>
private string GenerateConfirmationMessage(string protocol, string url)
{
var category = GetProtocolCategory(protocol);
string protocolDescription = GetProtocolDescription(protocol);
string safeUrl = url.Length > 100 ? url.Substring(0, 100) + "..." : url;
return $"网页正在尝试打开外部应用程序:\n\n" +
$"协议类型:{protocolDescription}\n" +
$"目标链接:{safeUrl}\n\n" +
$"是否允许执行此操作?\n\n" +
$"注意:只有信任的网站才应该被允许执行此类操作。";
}
/// <summary>
/// 获取协议描述
/// </summary>
/// <param name="protocol">协议类型</param>
/// <returns>协议描述</returns>
private string GetProtocolDescription(string protocol)
{
switch (protocol.ToLower())
{
case "bdnetdisk": return "百度网盘下载";
case "thunder": return "迅雷下载";
case "mailto": return "邮件客户端";
case "tel": return "电话应用";
case "skype": return "Skype通话";
case "steam": return "Steam游戏平台";
default: return $"{protocol.ToUpper()} 协议";
}
}📝 日志记录系统
协议执行日志
csharp
/// <summary>
/// 协议执行日志条目
/// </summary>
public class ProtocolExecutionLogEntry
{
public DateTime Timestamp { get; set; }
public string Protocol { get; set; }
public string Url { get; set; }
public string SourceUrl { get; set; }
public bool Allowed { get; set; }
public string Reason { get; set; }
public ProtocolCategory Category { get; set; }
}
/// <summary>
/// 协议执行日志列表
/// </summary>
private readonly List<ProtocolExecutionLogEntry> protocolLogs =
new List<ProtocolExecutionLogEntry>();
/// <summary>
/// 记录协议执行日志
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">协议URL</param>
/// <param name="allowed">是否允许执行</param>
/// <param name="sourceUrl">来源URL</param>
/// <param name="reason">决策原因</param>
private void LogProtocolExecution(string protocol, string url, bool allowed,
string sourceUrl = null, string reason = null)
{
if (!config.EnableLogging)
return;
var logEntry = new ProtocolExecutionLogEntry
{
Timestamp = DateTime.Now,
Protocol = protocol,
Url = url,
SourceUrl = sourceUrl ?? "Unknown",
Allowed = allowed,
Reason = reason ?? (allowed ? "Policy allowed" : "Policy blocked"),
Category = GetProtocolCategory(protocol)
};
// 记录到控制台
Console.WriteLine($"[{logEntry.Timestamp:yyyy-MM-dd HH:mm:ss}] " +
$"协议: {protocol} | 状态: {(allowed ? "允许" : "阻止")} | " +
$"来源: {GetDomainFromUrl(sourceUrl)}");
// 记录到内存列表
protocolLogs.Add(logEntry);
// 记录到文件
WriteProtocolLogToFile(logEntry);
// 定期清理旧日志
if (protocolLogs.Count % 100 == 0)
{
CleanupOldLogs();
}
}
/// <summary>
/// 将协议日志写入文件
/// </summary>
/// <param name="logEntry">日志条目</param>
private void WriteProtocolLogToFile(ProtocolExecutionLogEntry logEntry)
{
try
{
var logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "FBroLogs");
if (!Directory.Exists(logDir))
{
Directory.CreateDirectory(logDir);
}
var logFile = Path.Combine(logDir, $"ProtocolExecution_{DateTime.Now:yyyyMMdd}.log");
var logLine = $"{logEntry.Timestamp:yyyy-MM-dd HH:mm:ss}|{logEntry.Protocol}|" +
$"{(logEntry.Allowed ? "ALLOWED" : "BLOCKED")}|{logEntry.Category}|" +
$"{logEntry.Url}|{logEntry.SourceUrl}|{logEntry.Reason}";
File.AppendAllText(logFile, logLine + Environment.NewLine, System.Text.Encoding.UTF8);
}
catch (Exception ex)
{
Console.WriteLine($"写入协议日志失败: {ex.Message}");
}
}
/// <summary>
/// 清理旧日志
/// </summary>
/// <param name="daysToKeep">保留天数</param>
private void CleanupOldLogs(int daysToKeep = 30)
{
var cutoffDate = DateTime.Now.AddDays(-daysToKeep);
protocolLogs.RemoveAll(log => log.Timestamp < cutoffDate);
}
/// <summary>
/// 获取协议统计报告
/// </summary>
/// <returns>统计报告</returns>
public string GetProtocolStatisticsReport()
{
var report = new System.Text.StringBuilder();
report.AppendLine("=== 协议执行统计报告 ===");
report.AppendLine($"统计时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
report.AppendLine($"总协议请求数: {protocolLogs.Count}");
if (protocolLogs.Any())
{
var allowedCount = protocolLogs.Count(log => log.Allowed);
var blockedCount = protocolLogs.Count - allowedCount;
report.AppendLine($"允许执行: {allowedCount} ({(double)allowedCount / protocolLogs.Count * 100:F1}%)");
report.AppendLine($"阻止执行: {blockedCount} ({(double)blockedCount / protocolLogs.Count * 100:F1}%)");
report.AppendLine("\n--- 按协议类型统计 ---");
var protocolStats = protocolLogs.GroupBy(log => log.Protocol)
.OrderByDescending(g => g.Count())
.Take(10);
foreach (var group in protocolStats)
{
var allowed = group.Count(log => log.Allowed);
report.AppendLine($"{group.Key}: {group.Count()}次 (允许{allowed}次)");
}
report.AppendLine("\n--- 按协议分类统计 ---");
var categoryStats = protocolLogs.GroupBy(log => log.Category)
.OrderByDescending(g => g.Count());
foreach (var group in categoryStats)
{
var allowed = group.Count(log => log.Allowed);
report.AppendLine($"{group.Key}: {group.Count()}次 (允许{allowed}次)");
}
}
report.AppendLine("================================");
return report.ToString();
}🎯 实际应用场景
场景1:下载工具集成
csharp
/// <summary>
/// 下载工具集成处理器
/// 专门处理各种下载协议
/// </summary>
public class DownloadProtocolHandler
{
private readonly Dictionary<string, string> downloadClients = new Dictionary<string, string>
{
{ "bdnetdisk", "百度网盘" },
{ "thunder", "迅雷" },
{ "flashget", "快车" },
{ "qqdl", "QQ旋风" }
};
public override void OnProtocolExecution(IFBroSharpBrowser browser,
IFBroSharpFrame frame,
IFBroSharpRequest request,
ref bool allow_os_execution)
{
string protocol = GetProtocolFromUrl(request.Url);
if (downloadClients.ContainsKey(protocol))
{
// 记录下载请求
LogDownloadRequest(protocol, request.Url, frame?.Url);
// 检查下载客户端是否已安装
if (IsDownloadClientInstalled(protocol))
{
allow_os_execution = true;
Console.WriteLine($"✅ 启动{downloadClients[protocol]}处理下载");
}
else
{
allow_os_execution = false;
Console.WriteLine($"❌ {downloadClients[protocol]}未安装,无法处理下载");
// 可选:提示用户安装下载客户端
NotifyClientNotInstalled(downloadClients[protocol]);
}
}
else
{
// 非下载协议,使用默认处理
allow_os_execution = false;
}
}
/// <summary>
/// 检查下载客户端是否已安装
/// </summary>
/// <param name="protocol">协议类型</param>
/// <returns>是否已安装</returns>
private bool IsDownloadClientInstalled(string protocol)
{
try
{
// 检查注册表中的协议关联
using (var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(protocol))
{
return key != null;
}
}
catch
{
return false;
}
}
/// <summary>
/// 记录下载请求
/// </summary>
private void LogDownloadRequest(string protocol, string url, string sourceUrl)
{
Console.WriteLine($"下载请求 - 协议: {protocol}, 来源: {GetDomainFromUrl(sourceUrl)}");
}
/// <summary>
/// 通知客户端未安装
/// </summary>
private void NotifyClientNotInstalled(string clientName)
{
// 可以显示提示消息或引导用户下载
Console.WriteLine($"提示: {clientName}未安装,请先安装相应的下载客户端");
}
}场景2:通信应用集成
csharp
/// <summary>
/// 通信应用集成处理器
/// 处理邮件、电话、即时通信等协议
/// </summary>
public class CommunicationProtocolHandler
{
public override void OnProtocolExecution(IFBroSharpBrowser browser,
IFBroSharpFrame frame,
IFBroSharpRequest request,
ref bool allow_os_execution)
{
string protocol = GetProtocolFromUrl(request.Url);
string url = request.Url;
switch (protocol.ToLower())
{
case "mailto":
allow_os_execution = HandleMailtoProtocol(url, frame?.Url);
break;
case "tel":
allow_os_execution = HandleTelProtocol(url, frame?.Url);
break;
case "skype":
allow_os_execution = HandleSkypeProtocol(url, frame?.Url);
break;
case "zoom":
allow_os_execution = HandleZoomProtocol(url, frame?.Url);
break;
default:
allow_os_execution = false;
break;
}
}
/// <summary>
/// 处理邮件协议
/// </summary>
private bool HandleMailtoProtocol(string url, string sourceUrl)
{
try
{
// 解析邮件地址和主题
var mailInfo = ParseMailtoUrl(url);
Console.WriteLine($"邮件协议 - 收件人: {mailInfo.To}, 主题: {mailInfo.Subject}");
// 检查是否为可信来源
if (IsTrustedEmailSource(sourceUrl))
{
Console.WriteLine("✅ 允许打开邮件客户端");
return true;
}
else
{
Console.WriteLine("❌ 来源不可信,拒绝打开邮件客户端");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"处理邮件协议失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 处理电话协议
/// </summary>
private bool HandleTelProtocol(string url, string sourceUrl)
{
try
{
string phoneNumber = url.Substring(4); // 移除 "tel:"
Console.WriteLine($"电话协议 - 号码: {phoneNumber}");
// 验证电话号码格式
if (IsValidPhoneNumber(phoneNumber))
{
Console.WriteLine("✅ 允许拨打电话");
return true;
}
else
{
Console.WriteLine("❌ 电话号码格式无效");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"处理电话协议失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 解析邮件URL
/// </summary>
private (string To, string Subject) ParseMailtoUrl(string url)
{
var uri = new Uri(url);
string to = uri.AbsolutePath;
string subject = "";
if (!string.IsNullOrEmpty(uri.Query))
{
var queryParams = System.Web.HttpUtility.ParseQueryString(uri.Query);
subject = queryParams["subject"] ?? "";
}
return (to, subject);
}
/// <summary>
/// 验证电话号码格式
/// </summary>
private bool IsValidPhoneNumber(string phoneNumber)
{
// 简单的电话号码验证
var cleanNumber = System.Text.RegularExpressions.Regex.Replace(phoneNumber, @"[^\d\+]", "");
return cleanNumber.Length >= 7 && cleanNumber.Length <= 15;
}
/// <summary>
/// 检查是否为可信的邮件来源
/// </summary>
private bool IsTrustedEmailSource(string sourceUrl)
{
if (string.IsNullOrEmpty(sourceUrl))
return false;
// 检查是否来自知名邮件服务或企业网站
string[] trustedEmailSources = {
"gmail.com", "outlook.com", "qq.com", "163.com",
"company.com" // 可以添加企业域名
};
try
{
var uri = new Uri(sourceUrl);
string domain = uri.Host.ToLower();
return trustedEmailSources.Any(trusted =>
domain.Equals(trusted) || domain.EndsWith("." + trusted));
}
catch
{
return false;
}
}
}场景3:安全控制模式
csharp
/// <summary>
/// 安全优先的协议处理器
/// 严格控制协议执行,防止恶意攻击
/// </summary>
public class SecureProtocolHandler
{
private readonly HashSet<string> secureProtocols = new HashSet<string>
{
"mailto", "tel" // 只允许这些基础协议
};
private readonly Dictionary<string, DateTime> protocolExecutionHistory =
new Dictionary<string, DateTime>();
private readonly TimeSpan rateLimitInterval = TimeSpan.FromMinutes(1);
private readonly int maxExecutionsPerInterval = 5;
public override void OnProtocolExecution(IFBroSharpBrowser browser,
IFBroSharpFrame frame,
IFBroSharpRequest request,
ref bool allow_os_execution)
{
string protocol = GetProtocolFromUrl(request.Url);
string sourceUrl = frame?.Url ?? "Unknown";
// 1. 协议白名单检查
if (!secureProtocols.Contains(protocol))
{
LogSecurityEvent($"阻止未授权协议: {protocol}", sourceUrl, request.Url);
allow_os_execution = false;
return;
}
// 2. 频率限制检查
if (IsRateLimited(protocol))
{
LogSecurityEvent($"协议执行频率过高: {protocol}", sourceUrl, request.Url);
allow_os_execution = false;
return;
}
// 3. 来源验证
if (!IsSecureSource(sourceUrl))
{
LogSecurityEvent($"不安全的来源: {sourceUrl}", sourceUrl, request.Url);
allow_os_execution = false;
return;
}
// 4. URL内容验证
if (!IsSecureProtocolUrl(protocol, request.Url))
{
LogSecurityEvent($"协议URL验证失败: {protocol}", sourceUrl, request.Url);
allow_os_execution = false;
return;
}
// 通过所有安全检查
RecordProtocolExecution(protocol);
allow_os_execution = true;
Console.WriteLine($"✅ 安全协议执行: {protocol}");
}
/// <summary>
/// 检查是否达到频率限制
/// </summary>
private bool IsRateLimited(string protocol)
{
var key = $"{protocol}_{DateTime.Now:yyyyMMddHHmm}";
if (protocolExecutionHistory.ContainsKey(key))
{
var executionTime = protocolExecutionHistory[key];
if (DateTime.Now - executionTime < rateLimitInterval)
{
// 检查执行次数
var recentExecutions = protocolExecutionHistory.Keys
.Count(k => k.StartsWith(protocol) &&
DateTime.Now - protocolExecutionHistory[k] < rateLimitInterval);
return recentExecutions >= maxExecutionsPerInterval;
}
}
return false;
}
/// <summary>
/// 记录协议执行
/// </summary>
private void RecordProtocolExecution(string protocol)
{
var key = $"{protocol}_{DateTime.Now:yyyyMMddHHmmss}";
protocolExecutionHistory[key] = DateTime.Now;
// 清理旧记录
var oldKeys = protocolExecutionHistory.Keys
.Where(k => DateTime.Now - protocolExecutionHistory[k] > TimeSpan.FromHours(1))
.ToList();
foreach (var oldKey in oldKeys)
{
protocolExecutionHistory.Remove(oldKey);
}
}
/// <summary>
/// 检查来源是否安全
/// </summary>
private bool IsSecureSource(string sourceUrl)
{
if (string.IsNullOrEmpty(sourceUrl) || sourceUrl == "Unknown")
return false;
try
{
var uri = new Uri(sourceUrl);
// 必须是HTTPS
if (uri.Scheme != "https")
return false;
// 检查域名是否在可信列表中
// 这里可以实现更复杂的域名验证逻辑
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 验证协议URL是否安全
/// </summary>
private bool IsSecureProtocolUrl(string protocol, string url)
{
switch (protocol)
{
case "mailto":
// 检查邮件地址是否包含恶意脚本
return !url.Contains("<script") && !url.Contains("javascript:");
case "tel":
// 检查电话号码是否合理
var phoneNumber = url.Substring(4);
return System.Text.RegularExpressions.Regex.IsMatch(phoneNumber, @"^[\d\-\+\(\)\s]+$");
default:
return false;
}
}
/// <summary>
/// 记录安全事件
/// </summary>
private void LogSecurityEvent(string message, string sourceUrl, string protocolUrl)
{
var logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] SECURITY: {message}";
logEntry += $" | Source: {sourceUrl} | Protocol: {protocolUrl}";
Console.WriteLine(logEntry);
// 写入安全日志文件
try
{
var logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "FBroLogs");
if (!Directory.Exists(logDir))
Directory.CreateDirectory(logDir);
var logFile = Path.Combine(logDir, $"Security_{DateTime.Now:yyyyMMdd}.log");
File.AppendAllText(logFile, logEntry + Environment.NewLine, System.Text.Encoding.UTF8);
}
catch (Exception ex)
{
Console.WriteLine($"写入安全日志失败: {ex.Message}");
}
}
}⚙️ 配置管理系统
配置文件管理
csharp
/// <summary>
/// 加载协议处理配置
/// </summary>
public void LoadConfig(string configPath = null)
{
try
{
configPath = configPath ?? Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"FBro", "ProtocolConfig.json");
if (File.Exists(configPath))
{
var json = File.ReadAllText(configPath, System.Text.Encoding.UTF8);
config = Newtonsoft.Json.JsonConvert.DeserializeObject<ProtocolExecutionConfig>(json)
?? new ProtocolExecutionConfig();
Console.WriteLine($"协议配置已加载: {configPath}");
}
else
{
SaveConfig(configPath);
Console.WriteLine($"创建默认协议配置: {configPath}");
}
}
catch (Exception ex)
{
Console.WriteLine($"加载协议配置失败: {ex.Message},使用默认配置");
config = new ProtocolExecutionConfig();
}
}
/// <summary>
/// 保存协议处理配置
/// </summary>
public void SaveConfig(string configPath = null)
{
try
{
configPath = configPath ?? Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"FBro", "ProtocolConfig.json");
var directory = Path.GetDirectoryName(configPath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
var json = Newtonsoft.Json.JsonConvert.SerializeObject(config, Newtonsoft.Json.Formatting.Indented);
File.WriteAllText(configPath, json, System.Text.Encoding.UTF8);
Console.WriteLine($"协议配置已保存: {configPath}");
}
catch (Exception ex)
{
Console.WriteLine($"保存协议配置失败: {ex.Message}");
}
}⚠️ 注意事项
1. 安全考虑
- 严格验证:对所有协议URL进行格式和内容验证
- 来源检查:确保协议请求来自可信的网站
- 用户确认:对敏感操作要求用户明确确认
- 频率限制:防止恶意网站频繁触发协议执行
2. 用户体验
- 合理提示:当阻止协议执行时,给用户明确的提示信息
- 配置灵活:允许用户自定义协议处理策略
- 日志透明:提供详细的日志记录供用户查看
3. 兼容性
- 注册表检查:验证外部程序是否已正确安装
- 协议标准:遵循标准的协议格式规范
- 错误处理:妥善处理协议执行失败的情况
4. 性能优化
- 缓存机制:缓存协议验证结果,避免重复计算
- 异步处理:对于复杂的验证逻辑,考虑异步执行
- 日志管理:定期清理旧的日志文件,避免占用过多磁盘空间
🏆 最佳实践
1. 分层安全策略
csharp
// 实现多层安全验证
if (!IsProtocolInWhitelist(protocol)) return false;
if (!IsTrustedSource(sourceUrl)) return false;
if (!ValidateProtocolFormat(url)) return false;
if (!CheckUserPermission(protocol)) return false;2. 详细的日志记录
csharp
// 记录所有关键信息
LogProtocolExecution(protocol, url, allowed, sourceUrl, decisionReason);3. 用户友好的错误处理
csharp
// 提供明确的错误信息和解决建议
if (!IsClientInstalled(protocol))
{
ShowInstallationGuide(protocol);
}4. 配置化的处理策略
csharp
// 使用配置文件控制行为,便于调整
if (config.RequireUserConfirmation)
{
allow_os_execution = RequestUserConfirmation(protocol, url);
}通过以上完整的实现方案,您可以安全、灵活地控制FBro浏览器中的协议处理行为,实现与各种外部应用程序的无缝集成,同时确保用户的安全和良好的使用体验。